Spring boot와 MongoDB로 파일 업로드 및 다운로드하기

✒️ 2025-05-28 13:21 내용 수정



GridFS

GridFS 사용이 적합한 경우

GridFS 사용이 적합하지 않은 경우


Spring boot와 GridFS

class GridFsConfiguration extends AbstractMongoClientConfiguration {

	// … further configuration omitted
	
	@Bean
	public GridFsTemplate gridFsTemplate() {
		return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
	}
}
class GridFsClient {

	// GridFsOperations를 해당 클래스에 의존 주입
	@Autowired
	GridFsOperations operations;

	// 파일을 GridFS에 저장하기
	@Test
	public void storeFileToGridFs() {
		// 파일의 메타 데이터
		FileMetadata metadata = new FileMetadata();
		// populate metadata
		Resource file = … // 파일이나 리소스 검색

		// InputStream, 파일 이름, 메타 데이터를 받아 저장
		operations.store(file.getInputStream(), "filename.txt", metadata);
	}
}
class GridFsClient {
	
	@Autowired
	GridFsOperations operations;

	// 파일 검색하기 - GridFsOperations 메소드
	@Test
	public void findFilesInGridFs() {
		GridFSFindIterable result = operations.find(query(whereFilename().is("filename.txt")));
	}

	// 파일 검색하기 - ResourcePatternResolver 인터페이스
	public GridFsResources[] readFilesFromGridFs() { 
		return operations.getResources("*.txt"); 
	}	
}

1. 프로젝트 설정하기

  1. 먼저 Spring boot 설정, Spring boot, MongoDB와 연동으로 프로젝트 기본 설정과 DB 연결을 진행한다.
  2. application.propertiesMultipartConfig 관련 설정을 추가한다.
spring.servlet.multipart.enabled = true
spring.servlet.multipart.max-file-size = 200MB
spring.servlet.multipart.max-request-size = 200MB
spring:  
	servlet:  
		multipart:  
			enabled: true
			max-file-size: 200MB  
			max-request-size: 200MB
  1. build.gradlecommons-io 의존성을 추가한다.
    • 2024.10.25 기준 2.17.0 버전이 최신 버전이다.
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    implementation 'org.springframework.boot:spring-boot-starter-web-services'  
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'  
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'  
    implementation 'commons-io:commons-io:2.17.0'   
}

2. 클래스 추가

package com.ase.serverckecklist.dto;  
  
import lombok.Getter;  
import lombok.Setter;  
  
@Getter  
@Setter  
public class LoadFile {  
    private String filename;  
    private String fileType;  
    private String fileSize;  
    private byte[] file;  
}

3. Service 추가

package com.ase.serverckecklist.service;  
  
import com.ase.serverckecklist.dto.LoadFile;  
import com.mongodb.BasicDBObject;  
import com.mongodb.DBObject;  
import com.mongodb.client.gridfs.model.GridFSFile;  
import lombok.RequiredArgsConstructor;  
import org.apache.commons.io.IOUtils;  
import org.springframework.data.mongodb.core.query.Criteria;  
import org.springframework.data.mongodb.core.query.Query;  
import org.springframework.data.mongodb.gridfs.GridFsOperations;  
import org.springframework.data.mongodb.gridfs.GridFsTemplate;  
import org.springframework.stereotype.Service;  
import org.springframework.web.multipart.MultipartFile;  
  
import java.io.IOException;  
  
@Service  
@RequiredArgsConstructor  
public class FileService {  
  
    private final GridFsTemplate template;  
    private final GridFsOperations operations;  
  
    // 파일 저장하기  
    public String addFile(MultipartFile file) throws IOException {  
        // 메타 데이터 생성  
        DBObject metadata = new BasicDBObject();  
        metadata.put("fileSize", file.getSize());  
  
        // GridFS에 파일 저장하기  
        // InputStream, filename, metadata 정보로 파일을 저장함  
        Object fileID = template.store(  
                file.getInputStream(), file.getOriginalFilename(),  
                file.getContentType(), metadata);  
  
        // 파일 id를 반환  
        return fileID.toString();  
    }  
  
    // 파일 가져오기  
    public LoadFile downloadFile(String id) throws IOException {  
        // GridFS에 저장된 파일 중 _id가 찾으려는 id와 일치하는 파일을 query        
        // GridFsCriteria 클래스로 Query를 정의할 수 있음  
        GridFSFile gridFSFile = template.findOne( new Query(Criteria.where("_id").is(id)) );  
  
        // 받아올 파일 정보를 저장한 객체  
        LoadFile loadFile = new LoadFile();  
  
        // GridFS에 검색한 파일이 존재하거나 메타 데이터가 존재할 때  
        if (gridFSFile != null || gridFSFile.getMetadata() != null) {  
            // 받아올 파일 객체에 GridFS의 정보들을 저장  
            loadFile.setFilename(gridFSFile.getFilename());  
            loadFile.setFileType(gridFSFile.getMetadata().get("_contentType").toString());  
            loadFile.setFileSize(gridFSFile.getMetadata().get("fileSize").toString());  
            loadFile.setFile(IOUtils.toByteArray(operations.getResource(gridFSFile).getInputStream()));  
        }  
  
        return loadFile;  
    }  
}

4. Controller 추가

package com.ase.serverckecklist.controller;  
  
import com.ase.serverckecklist.dto.LoadFile;  
import com.ase.serverckecklist.service.FileService;  
import lombok.RequiredArgsConstructor;  
import org.springframework.core.io.ByteArrayResource;  
import org.springframework.http.HttpHeaders;  
import org.springframework.http.HttpStatus;  
import org.springframework.http.MediaType;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.*;  
import org.springframework.web.multipart.MultipartFile;  
  
import java.io.IOException;  
  
@RestController  
@RequestMapping("file")  
@RequiredArgsConstructor  
public class FileController {  
  
    private final FileService fileService;  
  
    // GET  
    // 파일 다운로드하기  
    @GetMapping("/download/{id}")  
    public ResponseEntity<ByteArrayResource> download(@PathVariable("id") String id) 
    throws IOException {  
        LoadFile loadFile = fileService.downloadFile(id);  
        String filename = URLEncoder.encode(loadFile.getFilename(), StandardCharsets.UTF_8.toString());  
  
        return ResponseEntity.ok()  
                .contentType(MediaType.parseMediaType(loadFile.getFileType()))  
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8\""+filename+"\"")  
                .body(new ByteArrayResource(loadFile.getFile()));  
    }  
  
    // POST  
    // 파일 업로드하기  
    @PostMapping("/upload")  
    public ResponseEntity<?> upload(
	    @RequestParam("file")MultipartFile file) throws IOException {  
        // 응답의 body에 파일 id를 넣어 전송  
        return new ResponseEntity<>(fileService.addFile(file), HttpStatus.OK);  
    }  
}

5. HTML 추가

<!doctype html>  
<html lang="en" xmlns:th="https://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport"  
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
</head>  
<body>  
    <div>  
        <div>  
            <h2>File Upload / Download Example</h2>  
        </div>  
        <div>            
	        <form id="fileUploadForm">  
                <h3>File Upload</h3>  
                <input type="file" name="file" id="fileUploadInput" class="file-input" required>  
                <button type="submit" class="submit-btn">Upload</button>  
            </form>  
        </div>  
  
		<div>            
			<div id="downloadFileUrl"></div>  
            <img id="downloadFileImg" alt="이미지">  
        </div>  
    </div>  
  
	<script>        
		let uploadForm = document.querySelector("#fileUploadForm");  
        let uploadFormInput = document.querySelector("#fileUploadInput");  
        let downloadFile = document.querySelector("#downloadFileUrl");  
        let downloadFileImg = document.querySelector("#downloadFileImg");  
  
        // 파일 업로드  
        function uploadFile(file) {  
            // formData 생성  
            let formData = new FormData();  
            formData.append("file", file);  

			// 요청 객체 생성
            let req = new XMLHttpRequest();  
            req.open("POST", "http://localhost:9000/file/upload");
  
            req.onload = function () {  
                let response = req.responseText;  
  
                if (response !== null) {  
                    let downloadUrl = "http://localhost:9000/file/download/" + response;  
  
                    downloadFile.innerHTML = '<p>File Upoaded Successfully. <br/> <a href="' 
                    + downloadUrl + '" target="_self">Download File</a></p>';  
                    downloadFile.style.display = "block";  
                    downloadFileImg.src = downloadUrl;  
                } else {  
                    alert("Error Occured! No file returned");  
                }  
            }  
            req.send(formData);  
        }  
  
        // 제출 동작  
        uploadForm.addEventListener('submit', function (event) {  
            const files = uploadFormInput.files;  
  
            if (files.length !== 0 ) {  
                uploadFile(files[0]);  
                event.preventDefault();  
            } else {  
                alert('Please select a file')  
            }  
  
        }, true);  
    </script>  
</body>  
</html>

6. 테스트

  1. 어플리케이션을 실행하고 웹 브라우저에서 파일 업로드 페이지로 이동한다.
  2. 이미지를 선택하여 파일을 추가한다.

springboot_mongodb_file 1.png

  1. 전송이 잘 되었다면 파일 다운로드 링크와 함께 이미지에서도 파일이 출력된다.
    • 오른쪽 네트워크 탭을 보면 응답의 결과가 파일의 id로 뜬다.

springboot_mongodb_file 2.png

  1. MongoDB에서 collections에 접속하여 fs.files collection이 있는지 확인한다.
    • 업로드한 파일의 이름, 크기, chunkSixe, 업로드 날짜, 메타 데이터가 저장되어 있다.

springboot_mongodb_file 3.png

  1. fs.chunks에도 데이터가 저장되어 있는지 확인한다.
    • 실제 파일의 chunk들이 저장된 collection이다.

springboot_mongodb_file 4.png